/* @flow */

import {
  warn,
  nextTick,
  emptyObject,
  handleError,
  defineReactive
} from '../util/index'

import { createElement } from '../vdom/create-element'
import { installRenderHelpers } from './render-helpers/index'
import { resolveSlots } from './render-helpers/resolve-slots'
import VNode, { createEmptyVNode } from '../vdom/vnode'

import { isUpdatingChildComponent } from './lifecycle'
// 初始化渲染函数
export function initRender (vm: Component) {
  // 在 Vue 实例对象上添加实例属性 _vnode：默认为null
  vm._vnode = null 
  // 在 Vue 实例对象上添加实例属性 _staticTrees：默认为null
  vm._staticTrees = null

  const options = vm.$options
  // _parentVnode，context，_renderChildren 后面补充 
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // 上面代码的作用为在vm实例上添加三个属性：vm.$vnode， vm.$vnode，vm.$scopedSlots

  // 在 Vue 实例对象上添加了两个方法：vm._c 和 vm.$createElement，其实际上都是对内部函数 createElement（该函数用来创建虚拟节点）的包装
  // vm._c 用于编译器根据模板字符串生成的渲染函数的
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* 在实例上添加 vm.$attrs 以及 vm.$listeners 主要用于高阶组件*/
  if (process.env.NODE_ENV !== 'production') {
    // 在vue实例对象上 添加 $attrs 属性，即 vm.$attrs，通过defineReactive来定义(该属性为响应式的)
    // 非生产环境下：defineReactive的第4个参数是一个箭头函数，实际上为一个自定义setter，用于设置$attrs或$listeners时触发并执行。
    // 箭头函数的作用是当你尝试修改 vm.$attrs 属性的值时，打印一段信息：$attrs 属性是只读的
    // 最后一个参数shallow：true 表示该属性不会被进行深度观察
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      // isUpdatingChildComponent在lifecycle中被初始化为false,只有当 updateChildComponent 函数开始执行的时候会被更新为 true
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

export function renderMixin (Vue: Class<Component>) {
  // 以 Vue.prototype为参数调用了installRenderHelpers 函数，该函数的作用就是在Vue.prototype上添加一系列方法
  installRenderHelpers(Vue.prototype)
  // 在 Vue.prototype添加$nextTick方法
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }
  // 在 Vue.prototype添加_render方法
  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // 使用 call 方法指定函数的执行环境为 vm._renderProxy，可通过vm.$options.render查看渲染函数 
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}
